/*
 * Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.cloudphone.audio;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.SparseArray;

import com.huawei.cloudphone.common.CASLog;
import com.huawei.cloudphone.common.CasRemoteMessage;
import com.huawei.cloudphone.datacenter.NewPacketCallback;
import com.huawei.cloudphone.jniwrapper.OpusJNIWrapper;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * AudioTrackerCallback
 */
public class AudioTrackerCallback implements NewPacketCallback {

    private static final String TAG = "CASAudioTrackerCallback";
    private SparseArray<CasAudioPlayer> sTrackMap = new SparseArray<>(32);
    // Mute audio when running in background
    private ArrayList<Short> dataList;
    private int maxSize;
    private int fixedPid = 1001;
    // 波形速度
    private int mWaveSpeed = 700;

    public AudioTrackerCallback() {
        sTrackMap.clear();
    }

    @Override
    public void onNewPacket(byte[] data) {
        // put the message to its thread's message queue.
        short[] music = (!isBigEnd()) ? byteArray2ShortArrayLittle(data, data.length / 2) :
            byteArray2ShortArrayBig(data, data.length / 2);
        sendData(music, music.length);
        if (data.length <= 0) {
            return;
        }
        CasRemoteMessage msg = new CasRemoteMessage(data, 0, data.length);
        int pid = fixedPid;

        CasAudioPlayer casAudioPlayer = sTrackMap.get(pid, null);

        if (casAudioPlayer == null) {
            casAudioPlayer = new CasAudioPlayer(pid);
            sTrackMap.put(pid, casAudioPlayer);
        }

        casAudioPlayer.onReceiveMsg(msg);
    }

    private short[] byteArray2ShortArrayBig(byte[] data, int items) {
        short[] retVal = new short[items];
        for (int i = 0; i < retVal.length; i++) {
            retVal[i] = (short) ((data[i * 2 + 1] & 0xff) | (data[i * 2] & 0xff) << 8);
        }

        return retVal;
    }

    private short[] byteArray2ShortArrayLittle(byte[] data, int items) {
        short[] retVal = new short[items];
        for (int i = 0; i < retVal.length; i++) {
            retVal[i] = (short) ((data[i * 2] & 0xff) | (data[i * 2 + 1] & 0xff) << 8);
        }

        return retVal;
    }

    private boolean isBigEnd() {
        short i = 0x1;
        boolean bRet = ((i >> 8) == 0x1);
        return bRet;
    }

    /**
     * 设置数据的获取显示，设置最大的获取数，一般都是控件大小/线的间隔offset
     *
     * @param dataList 数据
     * @param maxSize  最大个数
     */
    public void setDataList(ArrayList<Short> dataList, int maxSize) {
        this.dataList = dataList;
        this.maxSize = maxSize;
    }

    private void sendData(short[] shorts, int readSize) {
        if (dataList != null) {
            int length = readSize / mWaveSpeed;
            int resultMax = 0;
            int resultMin = 0;
            for (int i = 0, k = 0; i < length; i++, k += mWaveSpeed) {
                for (int j = k, max = 0, min = 1000; j < k + mWaveSpeed; j++) {
                    if (shorts[j] > max) {
                        max = shorts[j];
                        resultMax = max;
                    } else if (shorts[j] < min) {
                        min = shorts[j];
                        resultMin = min;
                    }
                }
                if (dataList.size() > maxSize) {
                    dataList.remove(0);
                }
                dataList.add((short) resultMax);
            }
        }
    }

    public void closeAudioTrack() {
        int pid = fixedPid;

        CasAudioPlayer casAudioPlayer = sTrackMap.get(pid, null);
        if (casAudioPlayer != null) {
            casAudioPlayer.closeAudioTracker();
        }
        sTrackMap.clear();
        sTrackMap = null;
    }

    class CasAudioPlayer {
        private static final String TAG = "CasAudioPlayer";
        private int mPid;
        private AudioTrack mTrackPlayer = null;
        private long mDecoder = 0;
        private int mMode = AudioTrack.MODE_STREAM;
        private int mBufferLenPerSecond = 0;
        private int bufferSizeInBytes = 0;
        private float vl = 50;
        private float vr = 50;
        private static final int USERSIZE_PCM = 1920;
        private static final int USERSIZE_OPUS = 240;

        public CasAudioPlayer(int pid) {
            mPid = pid;
        }

        public void onReceiveMsg(CasRemoteMessage msg) {
            handleSet(msg);
            handleStart(msg);
            handleWrite(msg);
        }

        public void closeAudioTracker() {
            handleStop(null);
            handleDestruct(null);
        }

        private void handleSet(CasRemoteMessage msg) {
            if (mTrackPlayer != null) {
                return;
            }

            mDecoder = OpusJNIWrapper.createOpusDecoder();
            int streamType = AudioManager.STREAM_MUSIC;
            int sampleRateInHz = 48000;
            int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
            int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
            mMode = AudioTrack.MODE_STREAM;
            bufferSizeInBytes = 0;
            int channelCount = 2;
            int sampleSize = 2;

            mBufferLenPerSecond = sampleRateInHz * sampleSize * channelCount;

            int minBufferLen = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

            int bufferSizeOf200Ms = (mBufferLenPerSecond / 5 / minBufferLen + 1) * minBufferLen;

            if (bufferSizeInBytes < bufferSizeOf200Ms) {
                bufferSizeInBytes = bufferSizeOf200Ms;
            }

            try {
                mTrackPlayer = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat,
                    bufferSizeInBytes, mMode);
            } catch (IllegalArgumentException e) {
                CASLog.e(TAG, "failed to new audioTrackPlayer");
            }
        }

        private void handleStart(CasRemoteMessage msg) {
            if (mTrackPlayer == null) {
                return;
            }

            try {
                mTrackPlayer.play();
            } catch (IllegalStateException e) {
                CASLog.e(TAG, "audioTrackPlayer failed to play");
            }
        }

        private void handleWrite(CasRemoteMessage msg) {

            if (mTrackPlayer == null) {
                return;
            }

            int userSize = msg.getSize();
            byte[] buffer = msg.readBytes(userSize);

            if (userSize == USERSIZE_OPUS) {
                // decode and play
                short[] data = new short[bufferSizeInBytes];
                int dataLen = OpusJNIWrapper.opusDecode(mDecoder, buffer, userSize, data, bufferSizeInBytes * 2);
                if (dataLen < 0) {
                    CASLog.e(TAG, "OPUS decode fail" + "userSize : " + userSize + "inputBuffer : " + Arrays.toString(buffer)
                            + "outputBuffer :" + Arrays.toString(data));
                    return;
                }
                mTrackPlayer.write(data, 0, dataLen, AudioTrack.WRITE_NON_BLOCKING);
            } else if (userSize == USERSIZE_PCM) {
                mTrackPlayer.write(buffer, 0, userSize, AudioTrack.WRITE_NON_BLOCKING);
            } else {
                CASLog.e(TAG, "audioTrackPlayer failed to write, invalid userSize.");
            }
        }

        private void handleStop(CasRemoteMessage msg) {
            if (mTrackPlayer == null) {
                return;
            }

            try {
                mTrackPlayer.stop();
            } catch (IllegalStateException e) {
                CASLog.e(TAG, "audioTrackPlayer failed to stop");
            }
        }

        private void handleDestruct(CasRemoteMessage msg) {
            if (mTrackPlayer == null) {
                return;
            }

            if (mDecoder != 0) {
                OpusJNIWrapper.destroyOpusDecoder(mDecoder);
                mDecoder = 0;
            }
            if (sTrackMap != null && sTrackMap.get(mPid) != null) {
                sTrackMap.remove(mPid);
            }

            mTrackPlayer.release();
            mTrackPlayer = null;
        }
    }
}
